import Animator from '../animate/Animator';
import Engine from '../engine/Engine';
import EventKernel from '../kernel/EventKernel';
import BoxScene from '../scenes/BoxScene';
import { bindAll } from '../utils/utils';
import HandlerManager from '../handlers/HandlerManager';

import AnimateFunction, { AnimateEasing } from '../animate/AnimateFunction';

import {
    CameraOptions,
    defaultOptions,
    Options,
    EventMap,
    AnimateOptions,
} from '../typings/PanoramaType';
import Camera from '../camera/Camera';
import Point from '../geometry/Point';

type Type = keyof EventMap;

class Panorama extends EventKernel<EventMap> {
    private _options: Options;
    private _engine?: Engine;
    private _container?: HTMLElement;
    private _animator?: Animator;
    private _canvasContainer: HTMLElement;
    private _handlerManager: HandlerManager;

    constructor(options: Options) {
        super();
        bindAll(['_onResize'], this);
        this._options = { ...defaultOptions, ...options };
        this._canvasContainer = document.createElement('div');
        this._handlerManager = new HandlerManager(this);
        window.addEventListener('resize', this._onResize);
    }

    private _onResize(e: UIEvent) {
        if (!this._engine || !e.isTrusted) return;
        this._engine.resize();
        this._engine.render();
    }

    private _setupContainer(container: HTMLElement) {
        this._container = container;

        this._canvasContainer.className = 'canvas-container';
        this._canvasContainer.style.width = '100%';
        this._canvasContainer.style.height = '100%';
        container.appendChild(this._canvasContainer);
    }

    getEngine() {
        return this._engine;
    }

    getBearing() {
        return this._engine?.getCamera().getBearing();
    }

    getPitch() {
        return this._engine?.getCamera().getPitch();
    }

    getCenter() {
        return this._engine?.getCamera().getCenter();
    }

    mount(container: HTMLElement | string) {
        let dom;
        if (container instanceof HTMLElement) {
            dom = container;
        } else {
            dom = document.getElementById(container);
        }
        if (!dom) {
            throw new Error('container is not exit');
        }
        this._setupContainer(dom);
        this._engine = new Engine(this._canvasContainer, {
            bearing: this._options.bearing,
            pitch: this._options.pitch,
            center: this._options.center,
        });

        const scene = new BoxScene({
            url: 'assets',
        });
        this._engine.addScene(scene);
    }

    getContainer() {
        return this._container;
    }

    getCanvasContainer() {
        return this._canvasContainer;
    }

    jumpTo(params: CameraOptions = {}) {
        if (!this._engine) return;
        const camera = this._engine.getCamera();
        camera.set(params);
        this._engine.render();

        this._fireEvent(params, 'Start');
        this._fireEvent(params, '');
        this._fireEvent(params, 'End');
    }

    easeTo(params: AnimateOptions = {}) {
        this._animator && this._animator.stop();
        this._animator = this._easeTo(params);
    }

    private _easeTo(params: AnimateOptions) {
        if (!this._engine) return;
        const camera = this._engine.getCamera();
        const startBearing = camera.getBearing();
        const startPitch = camera.getPitch();
        const startCenter = camera.getCenter();
        const {
            center,
            bearing = startBearing,
            pitch = startPitch,
            duration = 500,
            easing = <AnimateEasing>'easeOutCirc',
        } = params;
        const endCenter = center ? new Point(center.x, center.y) : startCenter.clone();
        const diff = endCenter.clone().subtract(startCenter);
        this._fireEvent(params, 'Start');
        const animator = new Animator().ofNumber(0, 1, duration);
        AnimateFunction[easing] && animator.easing(easing);
        const endBearing = Camera.normalizeBearing(bearing, startBearing);
        animator.on('update', (e) => {
            if (!this._engine) return;
            const progress = e.num / 1;
            const cameraOptions: CameraOptions = {
                bearing: startBearing + (endBearing - startBearing) * progress,
                pitch: startPitch + (pitch - startPitch) * progress,
                center: startCenter.clone().add(diff.clone().multiply(progress)),
            };
            camera.set(cameraOptions);

            this._engine.render();
            this._fireEvent(params, '');
        }).on('complete', () => {
            delete this._animator;
            this._fireEvent(params, 'End');
            params.complete && params.complete();
        }).on('stop', () => {
            params.complete && params.complete();
        }).start();

        return animator;
    }

    stop() {
        if (this._animator) {
            this._animator.stop();
            delete this._animator;
        }
    }

    setBearing(bearing: number) {
        this.jumpTo({ bearing });
    }

    setPitch(pitch: number) {
        this.jumpTo({ pitch });
    }

    private _fireEvent(params: CameraOptions, suffix: 'Start' | 'End' | '') {
        const { bearing, pitch } = params;
        this.fire(<Type>`move${suffix}`);
        typeof bearing === 'number' && this.fire(<Type>`bearing${suffix}`);
        typeof pitch === 'number' && this.fire(<Type>`pitch${suffix}`);
    }

    get handlerManager() {
        return this._handlerManager;
    }
}

export default Panorama;
